package net.java.amateras.uml.classdiagram.agent;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import net.java.amateras.uml.classdiagram.ClassDiagramEditor;
import net.java.amateras.uml.classdiagram.model.CommonEntityModel;
import net.java.amateras.uml.editpart.ConnectionBendpoint;
import net.java.amateras.uml.model.AbstractUMLConnectionModel;
import net.java.amateras.uml.model.AbstractUMLEntityModel;
import net.java.amateras.uml.model.AbstractUMLModel;
import net.java.amateras.uml.model.RootModel;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;

import abstraction.AbstactMediator;
import concrete.AmaterasMediator;


public class PatternDesigner {
	
	private ClassDiagramEditor classDiagramEditor;
	private RootModel rootModel;
	protected AbstactMediator mediator;

	public PatternDesigner() {
		mediator = AmaterasMediator.getInstance();
	}

	public ClassDiagramEditor getClassDiagramEditor() {
		return classDiagramEditor;
	}

	public void setClassDiagramEditor(ClassDiagramEditor classDiagramEditor) {
		this.classDiagramEditor = classDiagramEditor;
	}

	public RootModel getRootModel() {
		return rootModel;
	}

	public void setRootModel(RootModel rootModel) {
		this.rootModel = rootModel;
	}
	
	/**
	 * Se fija si el vector modelsVector contiene la entidad entityModel
	 * @param modelsVector
	 * @param entityModel
	 * @return
	 */
	private boolean containsElement(Vector<AbstractUMLModel> modelsVector, CommonEntityModel entityModel) {
		for (Enumeration<AbstractUMLModel> e= modelsVector.elements(); e.hasMoreElements();) {
			AbstractUMLModel element = e.nextElement();
			if (element instanceof CommonEntityModel) {
				String elementName = ((CommonEntityModel) element).getName();
				String entityModelName = entityModel.getName();
				if (elementName.equals(entityModelName)) {
					return true;
				}		
			}
		}
		return false;
	}
	
	/**
	 * Copio los atributos y los metodos en el nuevo modelo
	 * @param newModels
	 * @param oldModel
	 */
	private void copyElementData(Vector<AbstractUMLModel> newModels, CommonEntityModel oldModel) {
		for (Enumeration<AbstractUMLModel> e= newModels.elements(); e.hasMoreElements();) {
			AbstractUMLModel newModel = e.nextElement();
			if (newModel instanceof CommonEntityModel) {
				String elementName = ((CommonEntityModel) newModel).getName();
				String entityModelName = oldModel.getName();
				if (elementName.equals(entityModelName)) {
					setAttributesAndMethods((CommonEntityModel)newModel,oldModel);						
				}		
			}
		}
		
	}

	/**
	 * Setea como hijo del nuevo modelo a los del viejo
	 * @param newModel
	 * @param oldModel
	 */
	private void setAttributesAndMethods(CommonEntityModel newModel, CommonEntityModel oldModel) {
		newModel.setChildren(oldModel.getChildren());
	}

	/**
	 * Agrega todos los nuevos elementos al diagrama y arregla el layout
	 * @param modelsVector
	 */
	public void addElements (Vector<AbstractUMLModel> modelsVector) {

		int x=0;;
		int y=0;;
		
		/**
		 * Deshabilito el agente
		 */
		mediator.enableAutoAnalysis(false);
		mediator.setAgentEnabled(false);
		
		/**
		 * Borro todos los elementos UML existentes antes de graficar los nuestros.
		 * No esta hecho con iterator porque se comporta raro ya que cuando ejecuto el command se va actualizando la lista de children.
		 */
		DeleteCommand deleteCommand = new DeleteCommand();
		deleteCommand.setRootModel(rootModel);
		List<AbstractUMLModel> children = rootModel.getChildren();
		int childrenNumber = children.size();
		AbstractUMLModel modelToDelete = null;
			
		for (int i=0; i<childrenNumber; i++) {
			modelToDelete=children.get(0);
			if (modelToDelete instanceof CommonEntityModel) {
				if (containsElement(modelsVector, (CommonEntityModel)modelToDelete)) {
					copyElementData(modelsVector, (CommonEntityModel)modelToDelete);
				}
				deleteCommand.setTargetModel(modelToDelete);
				deleteCommand.execute();
			}
		}
		
		/**
		 * Borro todos las acciones del agente para mantener sincronizados los modelos 
		 */
		mediator.removeAllActions();
			
		/**
		 * Grafico todos los elementos UML
		 */
		for (Iterator<AbstractUMLModel> iterator = modelsVector.iterator(); iterator.hasNext();) {
				AbstractUMLModel model = (AbstractUMLModel) iterator.next();			
				if (model instanceof AbstractUMLEntityModel) {
					((AbstractUMLEntityModel)model).setConstraint(new Rectangle(x, y, -1, -1));
					x += 200;
					if (x > 1000) {
						x = 0;
						y += 200;
					}
					
					ImportCommand command = new ImportCommand();
					command.setModel(model);
					command.execute();
								
				}	
		}
		
		
		/**
		 * Esto lo tuvimos que hacer para las relaciones unarias
		 * Como el algoritmo de auto layout no soprota self-loops, los elimino, hago el autolayout y despues los pongo de nuevo
		 * A su vez creo el bendpoint que viene roto
		 */
		//Saco
		for (Iterator<AbstractUMLModel> iterator = modelsVector.iterator(); iterator.hasNext();) {
			AbstractUMLModel model = (AbstractUMLModel) iterator.next();			
			if (model instanceof AbstractUMLConnectionModel) {
				if ((((AbstractUMLConnectionModel) model).getSource()).equals(((AbstractUMLConnectionModel)model).getTarget())) {
					((AbstractUMLConnectionModel) model).detachSource();
				}
			}			
		}
		
		//AutoLayout
		classDiagramEditor.getAutoLayoutAction().run();
		
		//Agrego
		for (Iterator<AbstractUMLModel> iterator = modelsVector.iterator(); iterator.hasNext();) {
			AbstractUMLModel model = (AbstractUMLModel) iterator.next();			
			if (model instanceof AbstractUMLConnectionModel) {
				if ((((AbstractUMLConnectionModel) model).getSource()).equals(((AbstractUMLConnectionModel)model).getTarget())) {
					((AbstractUMLConnectionModel) model).attachSource();
					CreateBendPointCommand command = new CreateBendPointCommand();
					command.setModel((AbstractUMLConnectionModel) model);
					command.setSourceBounds(((AbstractUMLConnectionModel) model).getSource().getConstraint());
					command.execute();
				}
			}			
		}
		
		/**
		 * Habilito el agente
		 */
		mediator.setAgentEnabled(true);
		mediator.enableAutoAnalysis(true);
		
	}
	
	private class ImportCommand extends Command {

		private AbstractUMLModel model;

		public void setModel(AbstractUMLModel model) {
			this.model = model;
		}

		public void execute() {
			rootModel.copyPresentation(model);
			rootModel.addChild(model);
		}

		public void undo() {
			rootModel.removeChild(model);
		}
	}
	
	private class CreateBendPointCommand extends Command {
		AbstractUMLConnectionModel model;

		private Rectangle bounds;

		public void setModel(AbstractUMLConnectionModel model) {
			this.model = model;
		}

		public void setSourceBounds(Rectangle bounds) {
			this.bounds = bounds;
		}

		public void execute() {
			super.execute();
			int width = bounds.width / 2 + 50;
			int height = -bounds.height / 2 - 50;
			ConnectionBendpoint bendpoint = new ConnectionBendpoint(
					new Dimension(0, 0), new Dimension(width, 0));
			model.addBendpoint(0, bendpoint);
			bendpoint = new ConnectionBendpoint(new Dimension(width, 0),
					new Dimension(width, height));
			model.addBendpoint(1, bendpoint);
			bendpoint = new ConnectionBendpoint(new Dimension(width, height),
					new Dimension(0, height));
			model.addBendpoint(2, bendpoint);
		}
	}
	
	private static class DeleteCommand extends Command {

		private AbstractUMLEntityModel container;

		private AbstractUMLModel model;

		private List<AbstractUMLConnectionModel> sourceConnections = new ArrayList<AbstractUMLConnectionModel>();

		private List<AbstractUMLConnectionModel> targetConnections = new ArrayList<AbstractUMLConnectionModel>();

		public void execute() {

			sourceConnections.addAll(((AbstractUMLEntityModel) model)
					.getModelSourceConnections());

			targetConnections.addAll(((AbstractUMLEntityModel) model)
					.getModelTargetConnections());
	
			for (int i = 0; i < sourceConnections.size(); i++) {
				AbstractUMLConnectionModel model = (AbstractUMLConnectionModel) sourceConnections
						.get(i);
				model.detachSource();
				model.detachTarget();
			}

			for (int i = 0; i < targetConnections.size(); i++) {
				AbstractUMLConnectionModel model = (AbstractUMLConnectionModel) targetConnections
						.get(i);
				model.detachSource();
				model.detachTarget();
			}
			container.removeChild(model);
		}

		public void setRootModel(Object root) {
			this.container = (AbstractUMLEntityModel) root;
		}

		public void setTargetModel(Object model) {
			this.model = (AbstractUMLModel) model;
		}

	}
	
	/**
	 * Singleton
	 */
	public static PatternDesigner getInstance() {
		return DrawerTestHolder.INSTANCE;
	}

	private static class DrawerTestHolder {
		private static final PatternDesigner INSTANCE = new PatternDesigner();
	}

}
